!
! SYS_SIZE is the number of clicks (16 bytes) to be loaded.
! 0x3000 is 0x30000 bytes = 196kB, more than enough for current
! versions of linux
!
#include <linux/config.h>
SYSSIZE = DEF_SYSSIZE
!
!	bootsect.s		(C) 1991 Linus Torvalds
!	modified by Drew Eckhardt
!
! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
! iself out of the way to address 0x90000, and jumps there.
!
! It then loads 'setup' directly after itself (0x90200), and the system
! at 0x10000, using BIOS interrupts. 
!
! NOTE! currently system is at most 8*65536 bytes long. This should be no
! problem, even in the future. I want to keep it simple. This 512 kB
! kernel size should be enough, especially as this doesn't contain the
! buffer cache as in minix
!
! The loader has been made as simple as possible, and continuos
! read errors will result in a unbreakable loop. Reboot by hand. It
! loads pretty fast by getting whole sectors at a time whenever possible.

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

/* 规划内存 */
SETUPLEN = 4				! nr of setup-sectors /* setup占用扇区数 */
BOOTSEG  = 0x07c0			! original address of boot-sector /* BIOS加载bootsect的地址 */
INITSEG  = DEF_INITSEG			! we move boot here - out of the way /* 0x9000, bootsect将要移到的地址 */
SETUPSEG = DEF_SETUPSEG			! setup starts here /* 0x9020, setup加载地址 */
SYSSEG   = DEF_SYSSEG			! system loaded at 0x10000 (65536). /* system内核加载的地址 */
ENDSEG   = SYSSEG + SYSSIZE		! where to stop loading /* system结束地址 */

! ROOT_DEV & SWAP_DEV are now written by "build".
ROOT_DEV = 0 /* 根文件系统设备号 */
SWAP_DEV = 0 /* 交换设备号, 都使用与系统引导时同样的设备 */

entry start
start:
	mov	ax,#BOOTSEG
	mov	ds,ax
	mov	ax,#INITSEG
	mov	es,ax
	mov	cx,#256 /* bootsetc 512字节，256 * 2 */
	sub	si,si /* ds:si源地址 */
	sub	di,di /* es:di目的地址 */
	rep
	movw
	jmpi	go,INITSEG /* 跳转到新bootsect接着执行, cs=0x9000,ip=go */

go:	mov	ax,cs		
	mov	dx,#0xfef4	! arbitrary value >>512 - disk parm size

	mov	ds,ax   /* ds=0x9000 */
	mov	es,ax   /* es=0x9000 */
	push	ax  /* bug, 因为下面修改了栈 */

	mov	ss,ax		! put stack at 0x9ff00 - 12. /* 这12字节用于后面存放新的软驱参数表 */
	mov	sp,dx   /* ss:sp=0x9000:0xfef4=0x9fef4 */
/*
 *	Many BIOS's default disk parameter tables will not 
 *	recognize multi-sector reads beyond the maximum sector number
 *	specified in the default diskette parameter tables - this may
 *	mean 7 sectors in some cases.
 *
 *	Since single sector reads are slow and out of the question,
 *	we must take care of this by creating new parameter tables
 *	(for the first disk) in RAM.  We will set the maximum sector
 *	count to 18 - the most we will encounter on an HD 1.44.  
 *
 *	High doesn't hurt.  Low does.
 *
 *	Segments are as follows: ds=es=ss=cs - INITSEG,
 *		fs = 0, gs = parameter table segment
 */


/* 下面修改软驱参数表，将最大扇区数改成18，软驱参数表的中断号是0x1E，所以中断向量是0x1E*4=0x78 */
	push	#0
	pop	fs      /* fs=0 */
	mov	bx,#0x78		! fs:bx is parameter table address
	seg fs
	lgs	si,(bx)			! gs:si is source /* gs:si=(fs:bx)=(0x78)= 软驱参数表地址 */

	mov	di,dx			! es:di is destination /* di=dx=0xfef4, es=0x9000, es:di=0x9000:0xfef4 */
	mov	cx,#6			! copy 12 bytes
	cld

	rep
	seg gs              /* es:di <- gs:si, 即软驱参数表移动到0x9fef4，共12字节 */
	movw

	mov	di,dx       /* di=dx=0xfef4, ds:di指向新的软驱参数表 */
	movb	4(di),*18		! patch sector count /* (ds:di+4)=18 */

	seg fs
	mov	(bx),di /* (fs:bx)=(0:0x78) <- di, 新软驱参数表地址的偏移地址 */
	seg fs
	mov	2(bx),es    /* (fs:bx+2) <- es, 新软驱参数表地址的段地址 */

	pop	ax      /* 本意是想恢复ax=0x9000，但是这里有bug，实际恢复不了 */
	mov	fs,ax
	mov	gs,ax
	
	xor	ah,ah			! reset FDC 
	xor	dl,dl
	int 	0x13	/* 直接磁盘服务， ah=0磁盘系统复位，dl=0第一个软盘，00~7f:软盘，80~ff:硬盘 */

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.

load_setup:
	xor	dx, dx			! drive 0, head 0
	mov	cx,#0x0002		! sector 2, track 0
	mov	bx,#0x0200		! address = 512, in INITSEG
	mov	ax,#0x0200+SETUPLEN	! service 2, nr of sectors
	int	0x13			! read it
	jnc	ok_load_setup		! ok - continue /* 读扇区操作返回值，CF=0成功，AH=0,AL=传输的扇区数，CF!=0,出错，AH=状态代码 */

	push	ax			! dump error code /* 读扇区出错，AH中有状态代码 */
	call	print_nl    /* 打印回车换行 */
	mov	bp, sp
	call	print_hex   /* 打印ss:bp处的数据, 其实就是读盘的错误代码 */
	pop	ax	
	
	xor	dl, dl			! reset FDC
	xor	ah, ah
	int	0x13
	j	load_setup  /* 复位磁盘，继续读，没有退路 */

ok_load_setup:

! Get disk drive parameters, specifically nr of sectors/track

	xor	dl,dl
	mov	ah,#0x08		! AH=8 is get drive parameters
	int	0x13            /* 获取第一个软盘的参数, 实际上是取每磁道扇区数, CF=1出错，ah=状态码，ch=最大磁道号低8位，cl=每磁道最大扇区数（0-5bit）,最大磁道号高两位（6-7bit),dh=最大磁头数，dl=驱动器数量 */
	xor	ch,ch           /* 不需要最大磁道号参数 */
	seg cs
	mov	sectors,cx      /* cs:sectors <- cx, 此时ch=0，cl高两bit也为0，所以cx是每磁道最大扇区数 */
	mov	ax,#INITSEG     /* ax=0x9000 */
	mov	es,ax           /* es=ax=0x9000, 上面去磁盘参数改了es值，这里改回去 */

! Print some inane message

	mov	ah,#0x03		! read cursor pos   /* 0x10号中断，的ah=0x03号服务，读光标位置 */
	xor	bh,bh       /* bh=页号 */
	int	0x10        /* 读光标位置, 返回：ch=扫描开始线，cl=扫描结束线，dh=行号（0x00顶端），dl=列号（0x00最左边） */
	
	mov	cx,#9       /* 显示的字符串字符数 */
	mov	bx,#0x0007		! page 0, attribute 7 (normal) /* bh=显示页面号，bl=字符属性 */
	mov	bp,#msg1        /* es:bp指向要显示的字符串 */
	mov	ax,#0x1301		! write string, move cursor /* 0x10号中断，ah=0x13号服务，al=放置光标的方式及属性，0x01表示使用bl中的属性值，光标停在字符串结尾处 */
	int	0x10    /* dh=行号，dl=列号，都在读光标位置时设置好了 */

! ok, we've written the message, now
! we want to load the system (at 0x10000)

	mov	ax,#SYSSEG
	mov	es,ax		! segment of 0x010000 /* es=ax=SYSSEG=0x1000 */
	call	read_it
	call	kill_motor
	call	print_nl    /* 打印回车换行 */

! After that we check which root-device to use. If the device is
! defined (!= 0), nothing is done and the given device is used.
! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
! on the number of sectors that the BIOS reports currently.

	seg cs
	mov	ax,root_dev     /* ax <- cs:root_dev */
	or	ax,ax
	jne	root_defined    /* ax!=0,跳转 */
	seg cs
	mov	bx,sectors      /* bx <- cs:sectors */
	mov	ax,#0x0208		! /dev/ps0 - 1.2Mb
	cmp	bx,#15
	je	root_defined    /* 每磁道扇区数为15，则为/dev/ps0设备 */
	mov	ax,#0x021c		! /dev/PS0 - 1.44Mb
	cmp	bx,#18
	je	root_defined    /* 每磁道扇区数为18，则为/dev/PS0设备 */
undef_root:
	jmp undef_root
root_defined:
	seg cs
	mov	root_dev,ax /* cs:root_dev <- ax */

! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:

	jmpi	0,SETUPSEG  /* 跳转到setup中执行，cs=SETUPSEG,ip=0 */

! This routine loads the system at address 0x10000, making sure
! no 64kB boundaries are crossed. We try to load it as fast as
! possible, loading whole tracks whenever we can.
!
! in:	es - starting address segment (normally 0x1000)
!
sread:	.word 1+SETUPLEN	! sectors read of current track
head:	.word 0			! current head
track:	.word 0			! current track

read_it:
	mov ax,es
	test ax,#0x0fff     /* es值必须位于64KB边界 */
die:	jne die			! es must be at 64kB boundary
	xor bx,bx		! bx is starting address within segment
rp_read:
	mov ax,es
	cmp ax,#ENDSEG		! have we loaded all yet?
	jb ok1_read         /* ax=es<ENDSEG，跳转 */
	ret
ok1_read:
	seg cs
	mov ax,sectors  /* ax <- cs:sectors, 这里放的是软盘的每磁道扇区数 */
	sub ax,sread    /* ax=ax-5, sread是已经读取的bootsect和setup扇区数 */
	mov cx,ax       /* cx=当前磁道未读取的扇区数 */
	shl cx,#9       /* cx=cx*512 */
	add cx,bx       /* cx+=bx, 此时cx=未读扇区的所有字节数 */
	jnc ok2_read    /* cf=0跳转，就是说cx小于64KB跳转 */
	je ok2_read     /* zf=0，跳转，就是说cx等于0，其实就是等于64KB也跳转 */
	xor ax,ax
	sub ax,bx       /* 0就是64KB的补值，0-bx */
	shr ax,#9       /* 计算最多能读的扇区数 */
ok2_read:
	call read_track /* 从当前磁道指定的扇区开始读所需要的扇区 */
	mov cx,ax       /* 该次操作已读取的扇区数 */
	add ax,sread    /* ax=当前磁道所有已读扇区数 */
	seg cs
	cmp ax,sectors  /* 比较所有已读扇区数与总扇区数 */
	jne ok3_read    /* 不相等跳转 */
	mov ax,#1
	sub ax,head
	jne ok4_read    /* ax-head结果不为0，说明head是0，也就是说当前是磁头0，跳转去读1磁头面 */
	inc track       /* ax-head=0,说明当前是1磁头面，则可以去读下一磁道了 */
ok4_read:
	mov head,ax     /* head=ax=1磁头面 */
	xor ax,ax
ok3_read:
	mov sread,ax    /* 当前磁道还有扇区没有读完，保存已读扇区 */
	shl cx,#9       /* 上次读取字节数 */
	add bx,cx       /* 调整当前段内数据开始位置 */
	jnc rp_read     /* CF=0,跳转，继续读 */
	mov ax,es       /* CF!=0,说明已经读取了64KB,调整当前段，为读下一段数据做准备 */
	add ah,#0x10    /* 将段基址指向下一个64KB处 */
	mov es,ax
	xor bx,bx       /* 从下一个64KB偏移为0处开始读 */
	jmp rp_read

read_track:
	pusha   /* 压栈AX, CX, DX, BX, original SP, BP, SI, and DI */
	pusha	
	mov	ax, #0xe2e 	! loading... message 2e = . /* 电传方式显示一个. */
	mov	bx, #7
 	int	0x10
	popa		

	mov dx,track    /* 当前磁道号 */
	mov cx,sread    /* 当前磁道上已读扇区数 */
	inc cx          /* 开始读取的扇区 */
	mov ch,dl       /* ch=当前磁道号 */
	mov dx,head     /* 当前磁头号 */
	mov dh,dl       /* dh=磁头号，dl=驱动器号（为0表示A驱动器） */
	and dx,#0x0100  /* 磁头号不大于1 */
	mov ah,#2       /* 读磁盘扇区功能号 */
	
	push	dx				! save for error dump
	push	cx
	push	bx
	push	ax

	int 0x13
	jc bad_rt       /* CF=1出错跳转 */
	add	sp, #8   	/* 没有出错，丢弃为保存错误信息而压入的信息 */
	popa
	ret

bad_rt:	push	ax				! save error code
	call	print_all			! ah = error, al = read
	
	
	xor ah,ah   /* 复位磁盘 */
	xor dl,dl
	int 0x13
	

	add	sp, #10     /* 出错信息已经打印了，这里就丢弃掉 */
	popa	
	jmp read_track  /* 再次重试 */

/*
 *	print_all is for debugging purposes.  
 *	It will print out all of the registers.  The assumption is that this is
 *	called from a routine, with a stack frame like
 *	dx 
 *	cx
 *	bx
 *	ax
 *	error
 *	ret <- sp
 *
*/
 
print_all:
	mov	cx, #5		! error code + 4 registers
	mov	bp, sp	

print_loop:
	push	cx		! save count left
	call	print_nl	! nl for readability
	jae	no_reg		! see if register name is needed
	
	mov	ax, #0xe05 + 0x41 - 1
	sub	al, cl
	int	0x10

	mov	al, #0x58 	! X
	int	0x10

	mov	al, #0x3a 	! :
	int	0x10

no_reg:
	add	bp, #2		! next register
	call	print_hex	! print it
	pop	cx
	loop	print_loop
	ret

print_nl:   /* 打印回车换行 */
	mov	ax, #0xe0d	! CR
	int	0x10
	mov	al, #0xa	! LF
	int 	0x10
	ret

/*
 *	print_hex is for debugging purposes, and prints the word
 *	pointed to by ss:bp in hexadecmial.
*/

print_hex:
	mov	cx, #4		! 4 hex digits
	mov	dx, (bp)	! load word into dx
print_digit:
	rol	dx, #4		! rotate so that lowest 4 bits are used
	mov	ah, #0xe	
	mov	al, dl		! mask off so we have only next nibble
	and	al, #0xf
	add	al, #0x30	! convert to 0 based digit, '0'
	cmp	al, #0x39	! check for overflow
	jbe	good_digit
	add	al, #0x41 - 0x30 - 0xa 	! 'A' - '0' - 0xa

good_digit:
	int	0x10
	loop	print_digit
	ret


/*
 * This procedure turns off the floppy drive motor, so
 * that we enter the kernel in a known state, and
 * don't have to worry about it later.
 */
kill_motor:
	push dx
	mov dx,#0x3f2   /* 软驱控制卡的数字输出寄存器端口，只写 */
	xor al, al
	outb            /* 将al中的内容输出到dx指定的端口去 */
	pop dx
	ret

sectors:
	.word 0

msg1:
	.byte 13,10
	.ascii "Loading"

.org 506
swap_dev:
	.word SWAP_DEV /* 506 507 */
root_dev:
	.word ROOT_DEV /* 508 509 */
boot_flag:
	.word 0xAA55

.text
endtext:
.data
enddata:
.bss
endbss:

